package task2_2;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;

/**
 * Created by IntelliJ IDEA.
 * User: ATroshanin
 * Date: 05.06.2006
 * Time: 15:13:23
 * To change this template use File | Settings | File Templates.
 */
public class GramarTest {

    private List lexems;

    private GrammarStack grammarStack;

    private GrammarElement [][]gramar;

    private GrammarElement [][]gramarStub = {
            {new GrammarElement("expression", "rule"), new GrammarElement("(", "symbol"), new GrammarElement("expression", "rule"), new GrammarElement(")", "symbol"), new GrammarElement("continue_expression", "rule")},
            {new GrammarElement("expression", "rule"), new GrammarElement("+", "symbol"), new GrammarElement("expression", "rule")},
            {new GrammarElement("expression", "rule"), new GrammarElement("-", "symbol"), new GrammarElement("expression", "rule")},
            {new GrammarElement("expression", "rule"), new GrammarElement("number", "number"), new GrammarElement("continue_expression", "rule")},
            {new GrammarElement("continue_expression", "rule"), new GrammarElement("+", "symbol"), new GrammarElement("expression", "rule")},
            {new GrammarElement("continue_expression", "rule"), new GrammarElement("-", "symbol"), new GrammarElement("expression", "rule")},
            {new GrammarElement("continue_expression", "rule"), new GrammarElement("*", "symbol"), new GrammarElement("expression", "rule")},
            {new GrammarElement("continue_expression", "rule"), new GrammarElement("/", "symbol"), new GrammarElement("expression", "rule")},
            {new GrammarElement("continue_expression", "rule")},
    };

    public GramarTest(String path) throws GrammarException{
        if (path != null) {
            fillGrammar(getRoot(path));
        } else {
            gramar = gramarStub;
        }
    }

    private Element getRoot(String path) throws GrammarException{
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(path);
            return doc.getDocumentElement();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
        throw  new GrammarException();
    }

    private void fillGrammar(Element rootEl) {

        NodeList rows = rootEl.getElementsByTagName("row");
        gramar = new GrammarElement[rows.getLength()][];
        for (int i=0; i<rows.getLength(); i++) {
            Element row = (Element) rows.item(i);
            NodeList grammElements = row.getElementsByTagName("grammar-element");
            gramar[i] = new GrammarElement[grammElements.getLength()];

            for (int k=0; k<grammElements.getLength(); k++) {
                Element grammEl = (Element) grammElements.item(k);
                gramar[i][k] = new GrammarElement(grammEl.getAttribute("value"), grammEl.getAttribute("type"));
            }
        }
    }

    public boolean test(List lexems) {
        this.lexems = lexems;
        grammarStack = new GrammarStack();
        grammarStack.push(new GrammarElement("expression", "rule"));
        for (int i=0; i<lexems.size(); i++) {
            Lexem lexem = (Lexem) lexems.get(i);
            if (!currentLexemIsValid(lexem)) {
                return false;
            }
        }
        return currentLexemIsValid(null);
    }

    private boolean currentLexemIsValid(Lexem lexem) {

        if (lexem == null && grammarStack.getStackSize() == 0) {
            return true;
        }

        if (grammarStack.getStackSize() == 0) {
            return false;
        }

        GrammarElement currentStckPosition = grammarStack.getLast();

        if (currentStckPosition.isRule()) {
            for (int k=0; k<gramar.length; k++) {
                if (gramar[k][0].equals(currentStckPosition)) {

                    if (gramar[k].length > 1) {

                        if (gramar[k][1].isRule()) {
                            grammarStack.pop();
                            for (int j=gramar[k].length-1; j>0; j--) {
                                grammarStack.push(gramar[k][j]);
                            }

                            if (currentLexemIsValid(lexem)) {
                                return true;
                            }

                            for (int j=gramar[k].length-1; j>0; j--) {
                                grammarStack.pop();
                            }

                            grammarStack.push(currentStckPosition);

                        } else if (lexem !=null && lexemEqualsGrammarElement(lexem, gramar[k][1])) {
                            grammarStack.pop();
                            for (int j=gramar[k].length-1; j>0; j--) {
                                grammarStack.push(gramar[k][j]);
                            }
                            return currentLexemIsValid(lexem);
                        }

                    } else {
                        grammarStack.pop();
                        return currentLexemIsValid(lexem);
                    }

                }
            }

        } else {
            grammarStack.pop();
            return lexemEqualsGrammarElement(lexem, currentStckPosition);
        }

        return false;
    }

    private boolean lexemEqualsGrammarElement(Lexem lex, GrammarElement grEl) {

        if (grEl.isSymbol()) {
            return grEl.getValue().equals(lex.getValue());
        } else if (grEl.isNumber()) {
            return lex.getType().equals(Lexem.TYPE_DIGIT);
        }

        return false;
    }

 //-------------------------------------------------------------
    private class GrammarStack {

        private List stack;

        public GrammarStack() {
            stack = new ArrayList();
        }

        public List getStack() {
            return stack;
        }

        public void push(GrammarElement value) {
            stack.add(value);
        }

        public GrammarElement getLast() {
            return (GrammarElement) stack.get(stack.size() - 1);
        }

        public void pop() {
            stack.remove(stack.size() - 1);
        }

        public int getStackSize() {
            return stack.size();
        }
    }

    private class GrammarElement {

        private final String NUMBER     = "number";
        private final String RULE       = "rule";
        private final String TERM       = "term";
        private final String FUNCTION   = "function";
        private final String SYMBOL     = "symbol";

        private String value;
        private String type;


        public GrammarElement(String value, String type) {
            this.value = value;
            this.type = type;
        }

        public String getValue() {
            return value;
        }

        public boolean isRule() {
            return type.equals(RULE);
        }

        public boolean isNumber() {
            return type.equals(NUMBER);
        }

        public boolean isSymbol() {
            return type.equals(SYMBOL);
        }

        public boolean isTerm() {
            return type.equals(TERM);
        }

        public boolean isFunction() {
            return type.equals(FUNCTION);
        }

        public boolean equals(Object obj) {
            GrammarElement grElObj = (GrammarElement) obj;
            return grElObj.value.equals(this.value) && grElObj.type.equals(this.type);
        }

        public String toString() {
            return "[value=" + value + ", type=" + type + "]";
        }
    }

}

